
/**
  ******************************************************************************
  * Copyright 2021 The grapilot Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       dev_mgr_thread.c
  * @author     baiyang
  * @date       2021-10-30
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include <stdio.h>
#include <string.h>
#include "dev_mgr.h"

#include <common/grapilot.h>
#include <common/time/gp_time.h>
#include <common/console/console.h>
/*-----------------------------------macro------------------------------------*/
#ifndef HAL_DEVICE_THREAD_STACK
#define HAL_DEVICE_THREAD_STACK 1024
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
bool bus_thread_started[2][4];              // i2c、spi总线线程是否启用标志，第一个检索为总线类型，0为spi，1为i2c，第二个检索为第几条总线
                                        // 例如，thread_started[0][0]，对应spi1, thread_started[1][0]，对应i2c1
                                        // thread_started[0][1]，对应spi2, thread_started[1][1]，对应i2c2

rt_thread_t bus_thread_ctx[2][4];          // i2c、spi总线线程指针
struct DeviceCallbackInfo* bus_callbacks[2][4];    // i2c、spi总线回调函数
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
/**
  * @brief       
  * @param[in]   us  
  * @param[out]  
  * @retval      
  * @note        
  */
rt_tick_t devmgr_tick_from_microseconds(uint32_t us)
{
    rt_tick_t tick;

    tick = RT_TICK_PER_SECOND * (us / 1000000);
    tick += (RT_TICK_PER_SECOND * (us % 1000000) + 999999) / 1000000;

    /* return the calculated tick */
    return tick;
}

/*
  per-bus callback thread
*/
void devmgr_spi1_thread(void *arg)
{
    struct DeviceCallbackInfo *spi1_callback = bus_callbacks[0][0];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = spi1_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = spi1_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_spi2_thread(void *arg)
{
    struct DeviceCallbackInfo *spi2_callback = bus_callbacks[0][1];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = spi2_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = spi2_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_spi3_thread(void *arg)
{
    struct DeviceCallbackInfo *spi3_callback = bus_callbacks[0][2];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = spi3_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = spi3_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_spi4_thread(void *arg)
{
    struct DeviceCallbackInfo *spi4_callback = bus_callbacks[0][3];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = spi4_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = spi4_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_i2c1_thread(void *arg)
{
    struct DeviceCallbackInfo *i2c1_callback = bus_callbacks[1][0];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = i2c1_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = i2c1_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_i2c2_thread(void *arg)
{
    struct DeviceCallbackInfo *i2c2_callback = bus_callbacks[1][1];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = i2c2_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = i2c2_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_i2c3_thread(void *arg)
{
    struct DeviceCallbackInfo *i2c3_callback = bus_callbacks[1][2];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = i2c3_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = i2c3_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

void devmgr_i2c4_thread(void *arg)
{
    struct DeviceCallbackInfo *i2c4_callback = bus_callbacks[1][3];

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = i2c4_callback; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }
                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = i2c4_callback; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 100usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;

}


bool devmgr_thread_create(int8_t bus_type, int8_t bus_index, const char *name)
{
    void (*bus_thread)(void *_arg) = NULL;
    rt_uint8_t  priority = 31;

    if (bus_type == 0) {
        priority = PRIORITY_SPI;

        switch (bus_index) {
        case 0:
            bus_thread = devmgr_spi1_thread;
            break;

        case 1:
            bus_thread = devmgr_spi2_thread;
            break;
            
        case 2:
            bus_thread = devmgr_spi3_thread;
            break;
            
        case 3:
            bus_thread = devmgr_spi4_thread;
            break;
            
        default:
            break;
        }
    }else if (bus_type == 1) {
        priority = PRIORITY_I2C;

        switch (bus_index) {
        case 0:
            bus_thread = devmgr_i2c1_thread;
            break;

        case 1:
            bus_thread = devmgr_i2c2_thread;
            break;
            
        case 2:
            bus_thread = devmgr_i2c3_thread;
            break;
            
        case 3:
            bus_thread = devmgr_i2c4_thread;
            break;
            
        default:
            break;
        }
    }

    bus_thread_ctx[bus_type][bus_index] = rt_thread_create(name, bus_thread, NULL, HAL_DEVICE_THREAD_STACK, priority, 1);

    return (bus_thread_ctx[bus_type][bus_index] != NULL);
}

/**
  * @brief       
  * @param[in]   dev  
  * @param[in]   period_usec  
  * @param[in]   cb  
  * @param[out]  
  * @retval      
  * @note        
  */
struct DeviceCallbackInfo *devmgr_register_periodic_callback(rt_device_t dev, uint32_t period_usec, void (*cb)(void *parameter), void *parameter)
{
    int8_t bus_type = -1;
    int8_t bus_index = -1;

    if (dev->type == RT_Device_Class_SPIDevice) {
        bus_type = 0;
    } else if (dev->type == RT_Device_Class_I2CBUS) {
        bus_type = 1;
    }
    
    if (strstr(dev->parent.name, "spi1") != NULL
        || strstr(dev->parent.name, "i2c1") != NULL) {
        bus_index = 0;
    } else if (strstr(dev->parent.name, "spi2") != NULL
        || strstr(dev->parent.name, "i2c2") != NULL) {
        bus_index = 1;
    } else if (strstr(dev->parent.name, "spi3") != NULL
        || strstr(dev->parent.name, "i2c3") != NULL) {
        bus_index = 2;
    } else if (strstr(dev->parent.name, "spi4") != NULL
        || strstr(dev->parent.name, "i2c4") != NULL) {
        bus_index = 3;
    }

    if (bus_type == -1 || bus_index == -1) {
        return NULL;
    }

    if (!bus_thread_started[bus_type][bus_index]) {
        bus_thread_started[bus_type][bus_index] = true;

        // setup a name for the thread
        const uint8_t name_len = 7;
        char *name = (char *)rt_malloc(name_len);

        switch (dev->type) {
        case RT_Device_Class_I2CBUS:
            snprintf(name, name_len, "I2C%d",
                     bus_index);
            break;

        case RT_Device_Class_SPIDevice:
            snprintf(name, name_len, "SPI%d",
                     bus_index);
            break;
        default:
            break;
        }

        if (!devmgr_thread_create(bus_type, bus_index, name)) {
            console_panic("Failed to create bus thread %s", name);
        }
    }
    struct DeviceCallbackInfo *callback = (struct DeviceCallbackInfo *)rt_malloc(sizeof(struct DeviceCallbackInfo));

    /* clean memory data of object */
    rt_memset(callback, 0, sizeof(struct DeviceCallbackInfo));

    if (callback == NULL) {
        return NULL;
    }

    callback->cb = cb;
    callback->parameter = parameter;
    callback->period_usec = period_usec;
    callback->next_usec = time_micros64() + period_usec;

    // add to linked list of callbacks on thread
    callback->next = bus_callbacks[bus_type][bus_index];
    bus_callbacks[bus_type][bus_index] = callback;

    return callback;
}

/*------------------------------------test------------------------------------*/


